// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "base/trace_event/memory_dump_manager.h"

#include <algorithm>
#include <utility>

#include "base/atomic_sequence_num.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/debug/stack_trace.h"
#include "base/memory/ptr_util.h"
#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/trace_event/heap_profiler.h"
#include "base/trace_event/heap_profiler_allocation_context_tracker.h"
#include "base/trace_event/heap_profiler_stack_frame_deduplicator.h"
#include "base/trace_event/heap_profiler_type_name_deduplicator.h"
#include "base/trace_event/malloc_dump_provider.h"
#include "base/trace_event/memory_dump_provider.h"
#include "base/trace_event/memory_dump_session_state.h"
#include "base/trace_event/memory_infra_background_whitelist.h"
#include "base/trace_event/process_memory_dump.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "build/build_config.h"
#include "gen/base/debug/debugging_flags.h"

#if defined(OS_ANDROID)
#include "base/trace_event/java_heap_dump_provider_android.h"
#endif

#if defined(OS_WIN)
#include "base/trace_event/winheap_dump_provider_win.h"
#endif

namespace base {
namespace trace_event {

    namespace {

        const int kTraceEventNumArgs = 1;
        const char* kTraceEventArgNames[] = { "dumps" };
        const unsigned char kTraceEventArgTypes[] = { TRACE_VALUE_TYPE_CONVERTABLE };

        StaticAtomicSequenceNumber g_next_guid;
        MemoryDumpManager* g_instance_for_testing = nullptr;

        // Callback wrapper to hook upon the completion of RequestGlobalDump() and
        // inject trace markers.
        void OnGlobalDumpDone(MemoryDumpCallback wrapped_callback,
            uint64_t dump_guid,
            bool success)
        {
            TRACE_EVENT_NESTABLE_ASYNC_END1(
                MemoryDumpManager::kTraceCategory, "GlobalMemoryDump",
                TRACE_ID_MANGLE(dump_guid), "success", success);

            if (!wrapped_callback.is_null()) {
                wrapped_callback.Run(dump_guid, success);
                wrapped_callback.Reset();
            }
        }

        // Proxy class which wraps a ConvertableToTraceFormat owned by the
        // |session_state| into a proxy object that can be added to the trace event log.
        // This is to solve the problem that the MemoryDumpSessionState is refcounted
        // but the tracing subsystem wants a std::unique_ptr<ConvertableToTraceFormat>.
        template <typename T>
        struct SessionStateConvertableProxy : public ConvertableToTraceFormat {
            using GetterFunctPtr = T* (MemoryDumpSessionState::*)() const;

            SessionStateConvertableProxy(
                scoped_refptr<MemoryDumpSessionState> session_state,
                GetterFunctPtr getter_function)
                : session_state(session_state)
                , getter_function(getter_function)
            {
            }

            void AppendAsTraceFormat(std::string* out) const override
            {
                return (session_state.get()->*getter_function)()->AppendAsTraceFormat(out);
            }

            void EstimateTraceMemoryOverhead(
                TraceEventMemoryOverhead* overhead) override
            {
                return (session_state.get()->*getter_function)()
                    ->EstimateTraceMemoryOverhead(overhead);
            }

            scoped_refptr<MemoryDumpSessionState> session_state;
            GetterFunctPtr const getter_function;
        };

    } // namespace

    // static
    const char* const MemoryDumpManager::kTraceCategory = TRACE_DISABLED_BY_DEFAULT("memory-infra");

    // static
    const char* const MemoryDumpManager::kLogPrefix = "Memory-infra dump";

    // static
    const int MemoryDumpManager::kMaxConsecutiveFailuresCount = 3;

    // static
    const uint64_t MemoryDumpManager::kInvalidTracingProcessId = 0;

    // static
    const char* const MemoryDumpManager::kSystemAllocatorPoolName =
#if defined(MALLOC_MEMORY_TRACING_SUPPORTED)
        MallocDumpProvider::kAllocatedObjects;
#elif defined(OS_WIN)
        WinHeapDumpProvider::kAllocatedObjects;
#else
        nullptr;
#endif

    // static
    MemoryDumpManager* MemoryDumpManager::GetInstance()
    {
        if (g_instance_for_testing)
            return g_instance_for_testing;

        return Singleton<MemoryDumpManager,
            LeakySingletonTraits<MemoryDumpManager>>::get();
    }

    // static
    void MemoryDumpManager::SetInstanceForTesting(MemoryDumpManager* instance)
    {
        g_instance_for_testing = instance;
    }

    MemoryDumpManager::MemoryDumpManager()
        : delegate_(nullptr)
        , is_coordinator_(false)
        , memory_tracing_enabled_(0)
        , tracing_process_id_(kInvalidTracingProcessId)
        , dumper_registrations_ignored_for_testing_(false)
        , heap_profiling_enabled_(false)
    {
        g_next_guid.GetNext(); // Make sure that first guid is not zero.

        // At this point the command line may not be initialized but we try to
        // enable the heap profiler to capture allocations as soon as possible.
        EnableHeapProfilingIfNeeded();
    }

    MemoryDumpManager::~MemoryDumpManager()
    {
        TraceLog::GetInstance()->RemoveEnabledStateObserver(this);
    }

    void MemoryDumpManager::EnableHeapProfilingIfNeeded()
    {
        //   if (heap_profiling_enabled_)
        //     return;
        //
        //   if (!CommandLine::InitializedForCurrentProcess() ||
        //       !CommandLine::ForCurrentProcess()->HasSwitch(
        //           switches::kEnableHeapProfiling))
        //     return;
        //
        //   std::string profiling_mode = CommandLine::ForCurrentProcess()
        //       ->GetSwitchValueASCII(switches::kEnableHeapProfiling);
        //   if (profiling_mode == "") {
        //     AllocationContextTracker::SetCaptureMode(
        //         AllocationContextTracker::CaptureMode::PSEUDO_STACK);
        //   }
        //   else if (profiling_mode == switches::kEnableHeapProfilingModeNative) {
        // #if HAVE_TRACE_STACK_FRAME_POINTERS && \
//     (BUILDFLAG(ENABLE_PROFILING) || !defined(NDEBUG))
        //     // We need frame pointers for native tracing to work, and they are
        //     // enabled in profiling and debug builds.
        //     AllocationContextTracker::SetCaptureMode(
        //         AllocationContextTracker::CaptureMode::NATIVE_STACK);
        // #else
        //     CHECK(false) << "'" << profiling_mode << "' mode for "
        //                  << switches::kEnableHeapProfiling << " flag is not supported "
        //                  << "for this platform / build type.";
        // #endif
        //   } else {
        //     CHECK(false) << "Invalid mode '" << profiling_mode << "' for "
        //                << switches::kEnableHeapProfiling << " flag.";
        //   }
        //
        //   for (auto mdp : dump_providers_)
        //     mdp->dump_provider->OnHeapProfilingEnabled(true);
        //   heap_profiling_enabled_ = true;
        //DebugBreak();
    }

    void MemoryDumpManager::Initialize(MemoryDumpManagerDelegate* delegate,
        bool is_coordinator)
    {
        {
            AutoLock lock(lock_);
            DCHECK(delegate);
            DCHECK(!delegate_);
            delegate_ = delegate;
            is_coordinator_ = is_coordinator;
            EnableHeapProfilingIfNeeded();
        }

// Enable the core dump providers.
#if defined(MALLOC_MEMORY_TRACING_SUPPORTED)
        RegisterDumpProvider(MallocDumpProvider::GetInstance(), "Malloc", nullptr);
#endif

#if defined(OS_ANDROID)
        RegisterDumpProvider(JavaHeapDumpProvider::GetInstance(), "JavaHeap",
            nullptr);
#endif

#if defined(OS_WIN)
        RegisterDumpProvider(WinHeapDumpProvider::GetInstance(), "WinHeap", nullptr);
#endif

        // If tracing was enabled before initializing MemoryDumpManager, we missed the
        // OnTraceLogEnabled() event. Synthetize it so we can late-join the party.
        bool is_tracing_already_enabled = TraceLog::GetInstance()->IsEnabled();
        TRACE_EVENT0(kTraceCategory, "init"); // Add to trace-viewer category list.
        TraceLog::GetInstance()->AddEnabledStateObserver(this);
        if (is_tracing_already_enabled)
            OnTraceLogEnabled();
    }

    void MemoryDumpManager::RegisterDumpProvider(
        MemoryDumpProvider* mdp,
        const char* name,
        scoped_refptr<SingleThreadTaskRunner> task_runner,
        MemoryDumpProvider::Options options)
    {
        options.dumps_on_single_thread_task_runner = true;
        RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options);
    }

    void MemoryDumpManager::RegisterDumpProvider(
        MemoryDumpProvider* mdp,
        const char* name,
        scoped_refptr<SingleThreadTaskRunner> task_runner)
    {
        // Set |dumps_on_single_thread_task_runner| to true because all providers
        // without task runner are run on dump thread.
        MemoryDumpProvider::Options options;
        options.dumps_on_single_thread_task_runner = true;
        RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options);
    }

    void MemoryDumpManager::RegisterDumpProviderWithSequencedTaskRunner(
        MemoryDumpProvider* mdp,
        const char* name,
        scoped_refptr<SequencedTaskRunner> task_runner,
        MemoryDumpProvider::Options options)
    {
        DCHECK(task_runner);
        options.dumps_on_single_thread_task_runner = false;
        RegisterDumpProviderInternal(mdp, name, std::move(task_runner), options);
    }

    void MemoryDumpManager::RegisterDumpProviderInternal(
        MemoryDumpProvider* mdp,
        const char* name,
        scoped_refptr<SequencedTaskRunner> task_runner,
        const MemoryDumpProvider::Options& options)
    {
        if (dumper_registrations_ignored_for_testing_)
            return;

        bool whitelisted_for_background_mode = IsMemoryDumpProviderWhitelisted(name);
        scoped_refptr<MemoryDumpProviderInfo> mdpinfo = new MemoryDumpProviderInfo(mdp, name, std::move(task_runner), options,
            whitelisted_for_background_mode);

        {
            AutoLock lock(lock_);
            bool already_registered = !dump_providers_.insert(mdpinfo).second;
            // This actually happens in some tests which don't have a clean tear-down
            // path for RenderThreadImpl::Init().
            if (already_registered)
                return;
        }

        if (heap_profiling_enabled_)
            mdp->OnHeapProfilingEnabled(true);
    }

    void MemoryDumpManager::UnregisterDumpProvider(MemoryDumpProvider* mdp)
    {
        UnregisterDumpProviderInternal(mdp, false /* delete_async */);
    }

    void MemoryDumpManager::UnregisterAndDeleteDumpProviderSoon(
        std::unique_ptr<MemoryDumpProvider> mdp)
    {
        UnregisterDumpProviderInternal(mdp.release(), true /* delete_async */);
    }

    void MemoryDumpManager::UnregisterDumpProviderInternal(
        MemoryDumpProvider* mdp,
        bool take_mdp_ownership_and_delete_async)
    {
        std::unique_ptr<MemoryDumpProvider> owned_mdp;
        if (take_mdp_ownership_and_delete_async)
            owned_mdp.reset(mdp);

        AutoLock lock(lock_);

        auto mdp_iter = dump_providers_.begin();
        for (; mdp_iter != dump_providers_.end(); ++mdp_iter) {
            if ((*mdp_iter)->dump_provider == mdp)
                break;
        }

        if (mdp_iter == dump_providers_.end())
            return; // Not registered / already unregistered.

        if (take_mdp_ownership_and_delete_async) {
            // The MDP will be deleted whenever the MDPInfo struct will, that is either:
            // - At the end of this function, if no dump is in progress.
            // - Either in SetupNextMemoryDump() or InvokeOnMemoryDump() when MDPInfo is
            //   removed from |pending_dump_providers|.
            DCHECK(!(*mdp_iter)->owned_dump_provider);
            (*mdp_iter)->owned_dump_provider = std::move(owned_mdp);
        } else if (subtle::NoBarrier_Load(&memory_tracing_enabled_)) {
            // If you hit this DCHECK, your dump provider has a bug.
            // Unregistration of a MemoryDumpProvider is safe only if:
            // - The MDP has specified a sequenced task runner affinity AND the
            //   unregistration happens on the same task runner. So that the MDP cannot
            //   unregister and be in the middle of a OnMemoryDump() at the same time.
            // - The MDP has NOT specified a task runner affinity and its ownership is
            //   transferred via UnregisterAndDeleteDumpProviderSoon().
            // In all the other cases, it is not possible to guarantee that the
            // unregistration will not race with OnMemoryDump() calls.
            DCHECK((*mdp_iter)->task_runner && (*mdp_iter)->task_runner->RunsTasksOnCurrentThread())
                << "MemoryDumpProvider \"" << (*mdp_iter)->name << "\" attempted to "
                << "unregister itself in a racy way. Please file a crbug.";
        }

        // The MDPInfo instance can still be referenced by the
        // |ProcessMemoryDumpAsyncState.pending_dump_providers|. For this reason
        // the MDPInfo is flagged as disabled. It will cause InvokeOnMemoryDump()
        // to just skip it, without actually invoking the |mdp|, which might be
        // destroyed by the caller soon after this method returns.
        (*mdp_iter)->disabled = true;
        dump_providers_.erase(mdp_iter);
    }

    void MemoryDumpManager::RequestGlobalDump(
        MemoryDumpType dump_type,
        MemoryDumpLevelOfDetail level_of_detail,
        const MemoryDumpCallback& callback)
    {
        // Bail out immediately if tracing is not enabled at all or if the dump mode
        // is not allowed.
        if (!UNLIKELY(subtle::NoBarrier_Load(&memory_tracing_enabled_)) || !IsDumpModeAllowed(level_of_detail)) {
            VLOG(1) << kLogPrefix << " failed because " << kTraceCategory
                    << " tracing category is not enabled or the requested dump mode is "
                       "not allowed by trace config.";
            if (!callback.is_null())
                callback.Run(0u /* guid */, false /* success */);
            return;
        }

        const uint64_t guid = TraceLog::GetInstance()->MangleEventId(g_next_guid.GetNext());

        // Creates an async event to keep track of the global dump evolution.
        // The |wrapped_callback| will generate the ASYNC_END event and then invoke
        // the real |callback| provided by the caller.
        TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(kTraceCategory, "GlobalMemoryDump",
            TRACE_ID_MANGLE(guid));
        MemoryDumpCallback wrapped_callback = Bind(&OnGlobalDumpDone, callback);

        // Technically there is no need to grab the |lock_| here as the delegate is
        // long-lived and can only be set by Initialize(), which is locked and
        // necessarily happens before memory_tracing_enabled_ == true.
        // Not taking the |lock_|, though, is lakely make TSan barf and, at this point
        // (memory-infra is enabled) we're not in the fast-path anymore.
        MemoryDumpManagerDelegate* delegate;
        {
            AutoLock lock(lock_);
            delegate = delegate_;
        }

        // The delegate will coordinate the IPC broadcast and at some point invoke
        // CreateProcessDump() to get a dump for the current process.
        MemoryDumpRequestArgs args = { guid, dump_type, level_of_detail };
        delegate->RequestGlobalMemoryDump(args, wrapped_callback);
    }

    void MemoryDumpManager::RequestGlobalDump(
        MemoryDumpType dump_type,
        MemoryDumpLevelOfDetail level_of_detail)
    {
        RequestGlobalDump(dump_type, level_of_detail, MemoryDumpCallback());
    }

    void MemoryDumpManager::CreateProcessDump(const MemoryDumpRequestArgs& args,
        const MemoryDumpCallback& callback)
    {
        TRACE_EVENT_NESTABLE_ASYNC_BEGIN0(kTraceCategory, "ProcessMemoryDump",
            TRACE_ID_MANGLE(args.dump_guid));

        // If argument filter is enabled then only background mode dumps should be
        // allowed. In case the trace config passed for background tracing session
        // missed the allowed modes argument, it crashes here instead of creating
        // unexpected dumps.
        if (TraceLog::GetInstance()
                ->GetCurrentTraceConfig()
                .IsArgumentFilterEnabled()) {
            CHECK_EQ(MemoryDumpLevelOfDetail::BACKGROUND, args.level_of_detail);
        }

        std::unique_ptr<ProcessMemoryDumpAsyncState> pmd_async_state;
        {
            AutoLock lock(lock_);

            // |dump_thread_| can be nullptr is tracing was disabled before reaching
            // here. SetupNextMemoryDump() is robust enough to tolerate it and will
            // NACK the dump.
            pmd_async_state.reset(new ProcessMemoryDumpAsyncState(
                args, dump_providers_, session_state_, callback,
                dump_thread_ ? dump_thread_->task_runner() : nullptr));

            // Safety check to prevent reaching here without calling RequestGlobalDump,
            // with disallowed modes. If |session_state_| is null then tracing is
            // disabled.
            CHECK(!session_state_ || session_state_->memory_dump_config().allowed_dump_modes.count(args.level_of_detail));
        }

        TRACE_EVENT_WITH_FLOW0(kTraceCategory, "MemoryDumpManager::CreateProcessDump",
            TRACE_ID_MANGLE(args.dump_guid),
            TRACE_EVENT_FLAG_FLOW_OUT);

        // Start the process dump. This involves task runner hops as specified by the
        // MemoryDumpProvider(s) in RegisterDumpProvider()).
        SetupNextMemoryDump(std::move(pmd_async_state));
    }

    // PostTask InvokeOnMemoryDump() to the dump provider's sequenced task runner. A
    // PostTask is always required for a generic SequencedTaskRunner to ensure that
    // no other task is running on it concurrently. SetupNextMemoryDump() and
    // InvokeOnMemoryDump() are called alternatively which linearizes the dump
    // provider's OnMemoryDump invocations.
    // At most one of either SetupNextMemoryDump() or InvokeOnMemoryDump() can be
    // active at any time for a given PMD, regardless of status of the |lock_|.
    // |lock_| is used in these functions purely to ensure consistency w.r.t.
    // (un)registrations of |dump_providers_|.
    void MemoryDumpManager::SetupNextMemoryDump(
        std::unique_ptr<ProcessMemoryDumpAsyncState> pmd_async_state)
    {
        HEAP_PROFILER_SCOPED_IGNORE;
        // Initalizes the ThreadLocalEventBuffer to guarantee that the TRACE_EVENTs
        // in the PostTask below don't end up registering their own dump providers
        // (for discounting trace memory overhead) while holding the |lock_|.
        TraceLog::GetInstance()->InitializeThreadLocalEventBufferIfSupported();

        // |dump_thread_| might be destroyed before getting this point.
        // It means that tracing was disabled right before starting this dump.
        // Anyway either tracing is stopped or this was the last hop, create a trace
        // event, add it to the trace and finalize process dump invoking the callback.
        if (!pmd_async_state->dump_thread_task_runner.get()) {
            if (pmd_async_state->pending_dump_providers.empty()) {
                VLOG(1) << kLogPrefix << " failed because dump thread was destroyed"
                        << " before finalizing the dump";
            } else {
                VLOG(1) << kLogPrefix << " failed because dump thread was destroyed"
                        << " before dumping "
                        << pmd_async_state->pending_dump_providers.back().get()->name;
            }
            pmd_async_state->dump_successful = false;
            pmd_async_state->pending_dump_providers.clear();
        }
        if (pmd_async_state->pending_dump_providers.empty())
            return FinalizeDumpAndAddToTrace(std::move(pmd_async_state));

        // Read MemoryDumpProviderInfo thread safety considerations in
        // memory_dump_manager.h when accessing |mdpinfo| fields.
        MemoryDumpProviderInfo* mdpinfo = pmd_async_state->pending_dump_providers.back().get();

        // If we are in background tracing, we should invoke only the whitelisted
        // providers. Ignore other providers and continue.
        if (pmd_async_state->req_args.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND && !mdpinfo->whitelisted_for_background_mode) {
            pmd_async_state->pending_dump_providers.pop_back();
            return SetupNextMemoryDump(std::move(pmd_async_state));
        }

        // If the dump provider did not specify a task runner affinity, dump on
        // |dump_thread_| which is already checked above for presence.
        SequencedTaskRunner* task_runner = mdpinfo->task_runner.get();
        if (!task_runner) {
            DCHECK(mdpinfo->options.dumps_on_single_thread_task_runner);
            task_runner = pmd_async_state->dump_thread_task_runner.get();
            DCHECK(task_runner);
        }

        if (mdpinfo->options.dumps_on_single_thread_task_runner && task_runner->RunsTasksOnCurrentThread()) {
            // If |dumps_on_single_thread_task_runner| is true then no PostTask is
            // required if we are on the right thread.
            return InvokeOnMemoryDump(pmd_async_state.release());
        }

        bool did_post_task = task_runner->PostTask(
            FROM_HERE, Bind(&MemoryDumpManager::InvokeOnMemoryDump, Unretained(this), Unretained(pmd_async_state.get())));

        if (did_post_task) {
            // Ownership is tranferred to InvokeOnMemoryDump().
            ignore_result(pmd_async_state.release());
            return;
        }

        // PostTask usually fails only if the process or thread is shut down. So, the
        // dump provider is disabled here. But, don't disable unbound dump providers.
        // The utility thread is normally shutdown when disabling the trace and
        // getting here in this case is expected.
        if (mdpinfo->task_runner) {
            LOG(ERROR) << "Disabling MemoryDumpProvider \"" << mdpinfo->name
                       << "\". Failed to post task on the task runner provided.";

            // A locked access is required to R/W |disabled| (for the
            // UnregisterAndDeleteDumpProviderSoon() case).
            AutoLock lock(lock_);
            mdpinfo->disabled = true;
        }

        // PostTask failed. Ignore the dump provider and continue.
        pmd_async_state->pending_dump_providers.pop_back();
        SetupNextMemoryDump(std::move(pmd_async_state));
    }

    // This function is called on the right task runner for current MDP. It is
    // either the task runner specified by MDP or |dump_thread_task_runner| if the
    // MDP did not specify task runner. Invokes the dump provider's OnMemoryDump()
    // (unless disabled).
    void MemoryDumpManager::InvokeOnMemoryDump(
        ProcessMemoryDumpAsyncState* owned_pmd_async_state)
    {
        HEAP_PROFILER_SCOPED_IGNORE;
        // In theory |owned_pmd_async_state| should be a scoped_ptr. The only reason
        // why it isn't is because of the corner case logic of |did_post_task|
        // above, which needs to take back the ownership of the |pmd_async_state| when
        // the PostTask() fails.
        // Unfortunately, PostTask() destroys the scoped_ptr arguments upon failure
        // to prevent accidental leaks. Using a scoped_ptr would prevent us to to
        // skip the hop and move on. Hence the manual naked -> scoped ptr juggling.
        auto pmd_async_state = WrapUnique(owned_pmd_async_state);
        owned_pmd_async_state = nullptr;

        // Read MemoryDumpProviderInfo thread safety considerations in
        // memory_dump_manager.h when accessing |mdpinfo| fields.
        MemoryDumpProviderInfo* mdpinfo = pmd_async_state->pending_dump_providers.back().get();

        DCHECK(!mdpinfo->task_runner || mdpinfo->task_runner->RunsTasksOnCurrentThread());

        bool should_dump;
        {
            // A locked access is required to R/W |disabled| (for the
            // UnregisterAndDeleteDumpProviderSoon() case).
            AutoLock lock(lock_);

            // Unregister the dump provider if it failed too many times consecutively.
            if (!mdpinfo->disabled && mdpinfo->consecutive_failures >= kMaxConsecutiveFailuresCount) {
                mdpinfo->disabled = true;
                LOG(ERROR) << "Disabling MemoryDumpProvider \"" << mdpinfo->name
                           << "\". Dump failed multiple times consecutively.";
            }
            should_dump = !mdpinfo->disabled;
        } // AutoLock lock(lock_);

        if (should_dump) {
            // Invoke the dump provider.
            TRACE_EVENT_WITH_FLOW1(kTraceCategory,
                "MemoryDumpManager::InvokeOnMemoryDump",
                TRACE_ID_MANGLE(pmd_async_state->req_args.dump_guid),
                TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
                "dump_provider.name", mdpinfo->name);

            // Pid of the target process being dumped. Often kNullProcessId (= current
            // process), non-zero when the coordinator process creates dumps on behalf
            // of child processes (see crbug.com/461788).
            ProcessId target_pid = mdpinfo->options.target_pid;
            MemoryDumpArgs args = { pmd_async_state->req_args.level_of_detail };
            ProcessMemoryDump* pmd = pmd_async_state->GetOrCreateMemoryDumpContainerForProcess(target_pid,
                args);
            bool dump_successful = mdpinfo->dump_provider->OnMemoryDump(args, pmd);
            mdpinfo->consecutive_failures = dump_successful ? 0 : mdpinfo->consecutive_failures + 1;
        }

        pmd_async_state->pending_dump_providers.pop_back();
        SetupNextMemoryDump(std::move(pmd_async_state));
    }

    // static
    void MemoryDumpManager::FinalizeDumpAndAddToTrace(
        std::unique_ptr<ProcessMemoryDumpAsyncState> pmd_async_state)
    {
        HEAP_PROFILER_SCOPED_IGNORE;
        DCHECK(pmd_async_state->pending_dump_providers.empty());
        const uint64_t dump_guid = pmd_async_state->req_args.dump_guid;
        if (!pmd_async_state->callback_task_runner->BelongsToCurrentThread()) {
            scoped_refptr<SingleThreadTaskRunner> callback_task_runner = pmd_async_state->callback_task_runner;
            callback_task_runner->PostTask(
                FROM_HERE, Bind(&MemoryDumpManager::FinalizeDumpAndAddToTrace, Passed(&pmd_async_state)));
            return;
        }

        TRACE_EVENT_WITH_FLOW0(kTraceCategory,
            "MemoryDumpManager::FinalizeDumpAndAddToTrace",
            TRACE_ID_MANGLE(dump_guid), TRACE_EVENT_FLAG_FLOW_IN);

        for (const auto& kv : pmd_async_state->process_dumps) {
            ProcessId pid = kv.first; // kNullProcessId for the current process.
            ProcessMemoryDump* process_memory_dump = kv.second.get();
            std::unique_ptr<TracedValue> traced_value(new TracedValue);
            process_memory_dump->AsValueInto(traced_value.get());
            traced_value->SetString("level_of_detail",
                MemoryDumpLevelOfDetailToString(
                    pmd_async_state->req_args.level_of_detail));
            const char* const event_name = MemoryDumpTypeToString(pmd_async_state->req_args.dump_type);

            std::unique_ptr<ConvertableToTraceFormat> event_value(
                std::move(traced_value));
            TRACE_EVENT_API_ADD_TRACE_EVENT_WITH_PROCESS_ID(
                TRACE_EVENT_PHASE_MEMORY_DUMP,
                TraceLog::GetCategoryGroupEnabled(kTraceCategory), event_name,
                trace_event_internal::kGlobalScope, dump_guid, pid,
                kTraceEventNumArgs, kTraceEventArgNames,
                kTraceEventArgTypes, nullptr /* arg_values */, &event_value,
                TRACE_EVENT_FLAG_HAS_ID);
        }

        bool tracing_still_enabled;
        TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTraceCategory, &tracing_still_enabled);
        if (!tracing_still_enabled) {
            pmd_async_state->dump_successful = false;
            VLOG(1) << kLogPrefix << " failed because tracing was disabled before"
                    << " the dump was completed";
        }

        if (!pmd_async_state->callback.is_null()) {
            pmd_async_state->callback.Run(dump_guid, pmd_async_state->dump_successful);
            pmd_async_state->callback.Reset();
        }

        TRACE_EVENT_NESTABLE_ASYNC_END0(kTraceCategory, "ProcessMemoryDump",
            TRACE_ID_MANGLE(dump_guid));
    }

    void MemoryDumpManager::OnTraceLogEnabled()
    {
        bool enabled;
        TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTraceCategory, &enabled);
        if (!enabled)
            return;

        // Initialize the TraceLog for the current thread. This is to avoid that the
        // TraceLog memory dump provider is registered lazily in the PostTask() below
        // while the |lock_| is taken;
        TraceLog::GetInstance()->InitializeThreadLocalEventBufferIfSupported();

        // Spin-up the thread used to invoke unbound dump providers.
        std::unique_ptr<Thread> dump_thread(new Thread("MemoryInfra"));
        if (!dump_thread->Start()) {
            LOG(ERROR) << "Failed to start the memory-infra thread for tracing";
            return;
        }

        const TraceConfig trace_config = TraceLog::GetInstance()->GetCurrentTraceConfig();
        scoped_refptr<MemoryDumpSessionState> session_state = new MemoryDumpSessionState;
        session_state->SetMemoryDumpConfig(trace_config.memory_dump_config());
        if (heap_profiling_enabled_) {
            // If heap profiling is enabled, the stack frame deduplicator and type name
            // deduplicator will be in use. Add a metadata events to write the frames
            // and type IDs.
            session_state->SetStackFrameDeduplicator(
                WrapUnique(new StackFrameDeduplicator));

            session_state->SetTypeNameDeduplicator(
                WrapUnique(new TypeNameDeduplicator));

            TRACE_EVENT_API_ADD_METADATA_EVENT(
                TraceLog::GetCategoryGroupEnabled("__metadata"), "stackFrames",
                "stackFrames",
                WrapUnique(new SessionStateConvertableProxy<StackFrameDeduplicator>(
                    session_state, &MemoryDumpSessionState::stack_frame_deduplicator)));

            TRACE_EVENT_API_ADD_METADATA_EVENT(
                TraceLog::GetCategoryGroupEnabled("__metadata"), "typeNames",
                "typeNames",
                WrapUnique(new SessionStateConvertableProxy<TypeNameDeduplicator>(
                    session_state, &MemoryDumpSessionState::type_name_deduplicator)));
        }

        {
            AutoLock lock(lock_);

            DCHECK(delegate_); // At this point we must have a delegate.
            session_state_ = session_state;

            DCHECK(!dump_thread_);
            dump_thread_ = std::move(dump_thread);

            subtle::NoBarrier_Store(&memory_tracing_enabled_, 1);

            // TODO(primiano): This is a temporary hack to disable periodic memory dumps
            // when running memory benchmarks until telemetry uses TraceConfig to
            // enable/disable periodic dumps. See crbug.com/529184 .
            if (!is_coordinator_ || CommandLine::ForCurrentProcess()->HasSwitch("enable-memory-benchmarking")) {
                return;
            }
        }

        // Enable periodic dumps if necessary.
        periodic_dump_timer_.Start(trace_config.memory_dump_config().triggers);
    }

    void MemoryDumpManager::OnTraceLogDisabled()
    {
        // There might be a memory dump in progress while this happens. Therefore,
        // ensure that the MDM state which depends on the tracing enabled / disabled
        // state is always accessed by the dumping methods holding the |lock_|.
        subtle::NoBarrier_Store(&memory_tracing_enabled_, 0);
        std::unique_ptr<Thread> dump_thread;
        {
            AutoLock lock(lock_);
            dump_thread = std::move(dump_thread_);
            session_state_ = nullptr;
        }

        // Thread stops are blocking and must be performed outside of the |lock_|
        // or will deadlock (e.g., if SetupNextMemoryDump() tries to acquire it).
        periodic_dump_timer_.Stop();
        if (dump_thread)
            dump_thread->Stop();
    }

    bool MemoryDumpManager::IsDumpModeAllowed(MemoryDumpLevelOfDetail dump_mode)
    {
        AutoLock lock(lock_);
        if (!session_state_)
            return false;
        return session_state_->memory_dump_config().allowed_dump_modes.count(
                   dump_mode)
            != 0;
    }

    uint64_t MemoryDumpManager::GetTracingProcessId() const
    {
        return delegate_->GetTracingProcessId();
    }

    MemoryDumpManager::MemoryDumpProviderInfo::MemoryDumpProviderInfo(
        MemoryDumpProvider* dump_provider,
        const char* name,
        scoped_refptr<SequencedTaskRunner> task_runner,
        const MemoryDumpProvider::Options& options,
        bool whitelisted_for_background_mode)
        : dump_provider(dump_provider)
        , name(name)
        , task_runner(std::move(task_runner))
        , options(options)
        , consecutive_failures(0)
        , disabled(false)
        , whitelisted_for_background_mode(whitelisted_for_background_mode)
    {
    }

    MemoryDumpManager::MemoryDumpProviderInfo::~MemoryDumpProviderInfo() { }

    bool MemoryDumpManager::MemoryDumpProviderInfo::Comparator::operator()(
        const scoped_refptr<MemoryDumpManager::MemoryDumpProviderInfo>& a,
        const scoped_refptr<MemoryDumpManager::MemoryDumpProviderInfo>& b) const
    {
        if (!a || !b)
            return a.get() < b.get();
        // Ensure that unbound providers (task_runner == nullptr) always run last.
        // Rationale: some unbound dump providers are known to be slow, keep them last
        // to avoid skewing timings of the other dump providers.
        return std::tie(a->task_runner, a->dump_provider) > std::tie(b->task_runner, b->dump_provider);
    }

    MemoryDumpManager::ProcessMemoryDumpAsyncState::ProcessMemoryDumpAsyncState(
        MemoryDumpRequestArgs req_args,
        const MemoryDumpProviderInfo::OrderedSet& dump_providers,
        scoped_refptr<MemoryDumpSessionState> session_state,
        MemoryDumpCallback callback,
        scoped_refptr<SingleThreadTaskRunner> dump_thread_task_runner)
        : req_args(req_args)
        , session_state(std::move(session_state))
        , callback(callback)
        , dump_successful(true)
        , callback_task_runner(ThreadTaskRunnerHandle::Get())
        , dump_thread_task_runner(std::move(dump_thread_task_runner))
    {
        pending_dump_providers.reserve(dump_providers.size());
        pending_dump_providers.assign(dump_providers.rbegin(), dump_providers.rend());
    }

    MemoryDumpManager::ProcessMemoryDumpAsyncState::~ProcessMemoryDumpAsyncState()
    {
    }

    ProcessMemoryDump* MemoryDumpManager::ProcessMemoryDumpAsyncState::
        GetOrCreateMemoryDumpContainerForProcess(ProcessId pid,
            const MemoryDumpArgs& dump_args)
    {
        auto iter = process_dumps.find(pid);
        if (iter == process_dumps.end()) {
            std::unique_ptr<ProcessMemoryDump> new_pmd(
                new ProcessMemoryDump(session_state, dump_args));
            iter = process_dumps.insert(std::make_pair(pid, std::move(new_pmd))).first;
        }
        return iter->second.get();
    }

    MemoryDumpManager::PeriodicGlobalDumpTimer::PeriodicGlobalDumpTimer() { }

    MemoryDumpManager::PeriodicGlobalDumpTimer::~PeriodicGlobalDumpTimer()
    {
        Stop();
    }

    void MemoryDumpManager::PeriodicGlobalDumpTimer::Start(
        const std::vector<TraceConfig::MemoryDumpConfig::Trigger>& triggers_list)
    {
        if (triggers_list.empty())
            return;

        // At the moment the periodic support is limited to at most one periodic
        // trigger per dump mode. All intervals should be an integer multiple of the
        // smallest interval specified.
        periodic_dumps_count_ = 0;
        uint32_t min_timer_period_ms = std::numeric_limits<uint32_t>::max();
        uint32_t light_dump_period_ms = 0;
        uint32_t heavy_dump_period_ms = 0;
        DCHECK_LE(triggers_list.size(), 3u);
        auto* mdm = MemoryDumpManager::GetInstance();
        for (const TraceConfig::MemoryDumpConfig::Trigger& config : triggers_list) {
            DCHECK_NE(0u, config.periodic_interval_ms);
            switch (config.level_of_detail) {
            case MemoryDumpLevelOfDetail::BACKGROUND:
                DCHECK(mdm->IsDumpModeAllowed(MemoryDumpLevelOfDetail::BACKGROUND));
                break;
            case MemoryDumpLevelOfDetail::LIGHT:
                DCHECK_EQ(0u, light_dump_period_ms);
                DCHECK(mdm->IsDumpModeAllowed(MemoryDumpLevelOfDetail::LIGHT));
                light_dump_period_ms = config.periodic_interval_ms;
                break;
            case MemoryDumpLevelOfDetail::DETAILED:
                DCHECK_EQ(0u, heavy_dump_period_ms);
                DCHECK(mdm->IsDumpModeAllowed(MemoryDumpLevelOfDetail::DETAILED));
                heavy_dump_period_ms = config.periodic_interval_ms;
                break;
            }
            min_timer_period_ms = std::min(min_timer_period_ms, config.periodic_interval_ms);
        }

        DCHECK_EQ(0u, light_dump_period_ms % min_timer_period_ms);
        light_dump_rate_ = light_dump_period_ms / min_timer_period_ms;
        DCHECK_EQ(0u, heavy_dump_period_ms % min_timer_period_ms);
        heavy_dump_rate_ = heavy_dump_period_ms / min_timer_period_ms;

        timer_.Start(FROM_HERE, TimeDelta::FromMilliseconds(min_timer_period_ms),
            base::Bind(&PeriodicGlobalDumpTimer::RequestPeriodicGlobalDump,
                base::Unretained(this)));
    }

    void MemoryDumpManager::PeriodicGlobalDumpTimer::Stop()
    {
        if (IsRunning()) {
            timer_.Stop();
        }
    }

    bool MemoryDumpManager::PeriodicGlobalDumpTimer::IsRunning()
    {
        return timer_.IsRunning();
    }

    void MemoryDumpManager::PeriodicGlobalDumpTimer::RequestPeriodicGlobalDump()
    {
        MemoryDumpLevelOfDetail level_of_detail = MemoryDumpLevelOfDetail::BACKGROUND;
        if (light_dump_rate_ > 0 && periodic_dumps_count_ % light_dump_rate_ == 0)
            level_of_detail = MemoryDumpLevelOfDetail::LIGHT;
        if (heavy_dump_rate_ > 0 && periodic_dumps_count_ % heavy_dump_rate_ == 0)
            level_of_detail = MemoryDumpLevelOfDetail::DETAILED;
        ++periodic_dumps_count_;

        MemoryDumpManager::GetInstance()->RequestGlobalDump(
            MemoryDumpType::PERIODIC_INTERVAL, level_of_detail);
    }

} // namespace trace_event
} // namespace base
